#include <QTcpSocket>
#include <QHostAddress>
#include <QDebug>
#include <QByteArray>
#include <QXmlStreamWriter>
#include <QXmlStreamReader>
#include <QDataStream>
#include <QCryptographicHash>
#include <QDate>
#include <limits>

#include "../server/Commands.hpp"

#include "Client.hpp"

Client::Client(QObject *parent) :
    QObject(parent),
    m_socket(nullptr)
{
    m_socket = new QTcpSocket(this);
    connect(m_socket, SIGNAL(connected()),
            this, SLOT(socketConnected()));
    connect(m_socket, SIGNAL(readyRead()),
            this, SLOT(readyRead()));
}

void Client::writeXmlMessageSize(QByteArray &data)
{
    QString dataLength = QString::number(data.size());
    while(dataLength.length() < std::numeric_limits<quint32>::digits10) {
        dataLength.prepend(QString("0"));
    }
    data.prepend(dataLength.toStdString().c_str(), dataLength.toStdString().size());
}

void Client::parseXml(QByteArray &data)
{
    QXmlStreamReader xml(data);
    while (!xml.atEnd()) {
        QXmlStreamReader::TokenType tokenType = xml.readNext();
        if (QXmlStreamReader::StartDocument == tokenType) {
            continue;
        }

        if (QXmlStreamReader::StartElement == tokenType) {
            if (trUtf8("command") == xml.name()) {
                this->parseCommand(xml);
            }
        }
    }
}

void Client::parseCommand(QXmlStreamReader &xml)
{
    /*if (COMMAND_AUTHORISATION ==
            xml.attributes().value(trUtf8("type")).toString().toShort()) {
        qDebug() << "Parsing authorisation answer";
        this->parseAnswerAuthorisation(xml);

    }*/
    switch (xml.attributes().value(trUtf8("type")).toString().toShort()) {
    case COMMAND_AUTHORISATION:
        qDebug() << "Parsing authorisation answer";
        this->parseAnswerAuthorisation(xml);
        break;
    case COMMAND_GET_ALL:
        qDebug() << "Parsing get all";
        this->parseAnswerAllTransport(xml);
    }
}

void Client::parseAnswerAuthorisation(QXmlStreamReader &xml)
{
    USER_RIGHT userRight;
    while (!xml.atEnd()) {
        xml.readNext();
        if (trUtf8("userRight") == xml.name() &&
                QXmlStreamReader::StartElement == xml.tokenType()) {
            xml.readNext();
            if (QXmlStreamReader::Characters == xml.tokenType()) {
                userRight = static_cast<USER_RIGHT>(xml.text().toString().toUInt());
            }
        }
    }
    if (NOT_AUTHORIZED == userRight)
        emit errorForHumans(trUtf8("Неверное имя и/или пароль пользователя"));
    emit settingsUserRight(userRight);
}

void Client::parseAnswerAllTransport(QXmlStreamReader &xml)
{
}

void Client::connectToHost(QString hostIP, quint16 port)
{
    QHostAddress hostAddress;
    if(m_socket->isOpen())
        return;
    if(!hostAddress.setAddress(hostIP)) {
        emit errorForHumans(trUtf8("Не вылидный IP"));
        return;
    }
    m_socket->connectToHost(hostAddress, port);
    if (m_socket->waitForConnected(1000)) {
        qDebug() << "Connected";
    }
    else {
        m_socket->close();
        qDebug() << "Not connected";
        emit errorForHumans(trUtf8("Тайм-аут подключеня"));
        emit settingsUserRight(NOT_AUTHORIZED);
        return;
    }
}

void Client::authorization(QString name, QString password)
{
    QByteArray data;
    QXmlStreamWriter stream(&data);
    QString dataLength;

    stream.setAutoFormatting(true);
    stream.writeStartDocument();

    stream.writeStartElement(trUtf8("command"));
    stream.writeAttribute(trUtf8("type"),
                          QString::number(static_cast<quint16>(COMMAND_AUTHORISATION)));//trUtf8("authorisation"));
    stream.writeStartElement(trUtf8("parametrs"));
    stream.writeTextElement(trUtf8("name"), name);
    auto passwordHash = password;//QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Md5);
    qDebug() << passwordHash;
    stream.writeTextElement(trUtf8("password"), passwordHash);
    stream.writeEndElement(); //parametrs
    stream.writeEndElement(); //command
    stream.writeEndDocument();

    writeXmlMessageSize(data);
    /*dataLength = QString::number(data.size());
    while(dataLength.length() < std::numeric_limits<quint32>::digits10) {
        dataLength.prepend(QString("0"));
    }
    data.prepend(dataLength.toStdString().c_str(), dataLength.toStdString().size());
    */
    qDebug() << data;

    m_socket->write(data);
}

void Client::allTransport()
{
    QByteArray data;
    QXmlStreamWriter xml(&data);

    xml.setAutoFormatting(true);

    xml.writeStartDocument();

    xml.writeStartElement(trUtf8("command"));
    xml.writeAttribute(trUtf8("type"),
                       QString::number(
                           static_cast<quint16>(
                               COMMAND_GET_ALL)));
    xml.writeStartElement(trUtf8("parametrs"));

    xml.writeEndElement(); //parametrs
    xml.writeEndElement(); //command

    xml.writeEndDocument();

    writeXmlMessageSize(data);

    qDebug() << data;

    m_socket->write(data);
}

void Client::searchOnNetxMonth(QDate date)
{
    QByteArray data;
    QXmlStreamWriter xml(&data);

    xml.setAutoFormatting(true);

    xml.writeStartDocument();

    xml.writeStartElement(trUtf8("command"));
    xml.writeAttribute(trUtf8("type"),
                       QString::number(
                           static_cast<quint16>(
                               COMMAND_SEARCH_ON_NEXT_MONTH)));
    xml.writeStartElement(trUtf8("parametrs"));
    xml.writeTextElement(trUtf8("date"),
                         date.toString("yyyy-MM-dd"));
    xml.writeEndElement(); //parametrs
    xml.writeEndElement(); //command

    xml.writeEndDocument();

    writeXmlMessageSize(data);

    qDebug() << data;
}

void Client::socketConnected()
{
    emit connected();
}

void Client::disconnectFromHost()
{
    m_socket->disconnectFromHost();
    m_socket->close();
}

void Client::readyRead()
{
    auto bytesAvailable = m_socket->bytesAvailable();
    if (bytesAvailable < sizeof(std::numeric_limits<quint32>::digits10))
        return;
    auto messageSize = m_socket->peek(std::numeric_limits<quint32>::digits10).toLongLong();
    if ((bytesAvailable - std::numeric_limits<quint32>::digits10) < messageSize)
        return;

    QByteArray data = m_socket->read(std::numeric_limits<quint32>::digits10);
    messageSize = data.toLongLong();
    data.clear();

    static const quint16 bufferSize = 1024;
    char buffer[bufferSize];
    auto leftBytes = m_socket->bytesAvailable();
    quint32 currentReadSize;

    (leftBytes >= bufferSize) ? currentReadSize = bufferSize
                              : currentReadSize = leftBytes;
    while (leftBytes) {
        auto readBytes = m_socket->read(buffer, currentReadSize);
        data.append(buffer, readBytes);
        leftBytes -= readBytes;
    }

    qDebug() << data;

    this->parseXml(data);
}

